package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.PointOutside;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Set;

import xj.graph2d.Arrow;
import xj.graph2d.ArrowAttr;
import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.DrawShapeSearch;
import xj.graph2d.GraphEdgeBase;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.Label;
import xj.graph2d.Offset;
import xj.graph2d.LineShape;
import xj.graph2d.SelfLoop;
import xj.graph2d.TwoEndsEdgeShape;
import xj.graph2d.TwoEndsShape;
import xj.lang.CodeWriter;

public class GraphEdgeShape 
  extends TwoEndsEdgeShape
  implements CurveShape, PolyShape, SelfLoop {

  public static final int DEFAULT         = 0; 
  public static final int LINE            = 1; 
  public static final int QUAD_CURVE      = 2; 
  public static final int CUBIC_CURVE     = 3; 
  public static final int POLY_LINE       = 4; 
  public static final int ORTHOGONAL_LINE = 5; 
  public static final int POLY_CURVE      = 6; 

  public static final int SELF_LOOP       = 7; 
  //public static final int PLOY_CURVE_SELF_LOOP = 8; 

  // no edge shape 
  public GraphEdgeShape() {
    this(false);
  }

  // no edge shape 
  public GraphEdgeShape(boolean createComp) {
    super(createComp);
    allowRotation = false;
  }

  // initialize with specified edge shape 
  // edge shape has no graph component 
  public GraphEdgeShape(int shape, boolean createComp) {
    this(createComp);

    switch (shape) { 
    case QUAD_CURVE:
      edge = new QuadCurveShape(false);
      break;

    case CUBIC_CURVE:
      edge = new CubicCurveShape(false);
      break;

    case POLY_LINE:
      edge = new PolyLineShape(false);
      break;

    case ORTHOGONAL_LINE:
      edge = new OrthogonalLineShape(false);
      break;

    case POLY_CURVE:
      edge = new PolyCurveShape(false);
      break; 

    case SELF_LOOP:
      edge = new OrthogonalSelfLoop(false);
      break; 

    case LINE:
    default:
      edge = new LineShape(false); // default edge shape
    }
    edge.setParent(this);
    allowSelfLoop = edge.isAllowSelfLoop();
  }

  public GraphEdgeShape(float x1, float y1, float x2, float y2, 
			int shape, boolean createComp) {
    this(shape, createComp);
    setEnds(x1, y1, x2, y2);
  }

  // line 
  public GraphEdgeShape(float x1, float y1, float x2, float y2) {
    this(x1, y1, x2, y2, LINE, false);
  }

  // line 
  public GraphEdgeShape(float x1, float y1, float x2, float y2, 
			boolean createComp) {
    this(x1, y1, x2, y2, LINE, createComp);
  }

  // poly line 
  public GraphEdgeShape(float x1, float y1) {
    this(x1, y1, false);
  }

  // poly line 
  public GraphEdgeShape(float x1, float y1, 
			boolean createComp) {
    this(createComp);

    edge = new PolyLineShape(x1, y1, false);
    allowSelfLoop = edge.isAllowSelfLoop();
    edge.setParent(this);
  }

  // orthogonal line 
  public GraphEdgeShape(boolean startVertical, float x1, float y1) {
    this(startVertical, x1, y1, false);
  }

  // orthogonal line 
  public GraphEdgeShape(boolean startVertical, 
			float x1, float y1, 
			boolean createComp) {
    this(createComp);

    edge = new OrthogonalLineShape(startVertical, x1, y1, false);
    allowSelfLoop = edge.isAllowSelfLoop();
    edge.setParent(this);
  }

  // quad curve
  public GraphEdgeShape(float x1, float y1, float x2, float y2, 
			float cx1, float cy1) {
    this(x1, y1, x2, y2, cx1, cy1, false);
  }

  // quad curve
  public GraphEdgeShape(float x1, float y1, float x2, float y2, 
			float cx1, float cy1, boolean createComp) {
    this(createComp);

    super.setEnds(x1, y1, x2, y2);
    edge = new QuadCurveShape(x1, y1, x2, y2, cx1, cy1, false);
    allowSelfLoop = edge.isAllowSelfLoop();
    edge.setParent(this);
  }

  // cubic curve 
  public GraphEdgeShape(float x1, float y1, float x2, float y2, 
			float cx1, float cy1, float cx2, float cy2) { 
    this(x1, y1, x2, y2, cx1, cy1, cx2, cy2, false);
  }

  // cubic curve 
  public GraphEdgeShape(float x1, float y1, float x2, float y2, 
			float cx1, float cy1, float cx2, float cy2, 
			boolean createComp) {
    this(createComp);

    super.setEnds(x1, y1, x2, y2);
    edge = new CubicCurveShape(x1, y1, x2, y2, cx1, cy1, cx2, cy2, false);
    allowSelfLoop = edge.isAllowSelfLoop();
    edge.setParent(this);
  }

  // self loop
  public GraphEdgeShape(boolean orthogonal, boolean onesided, 
			float x1, float y1, float x2, float y2,
			float width, float height, int direction) { 
    this(orthogonal, onesided, x1, y1, x2, y2, width, height, direction, false); 
  }

  // self loop
  public GraphEdgeShape(boolean orthogonal, boolean onesided, 
			float x1, float y1, float x2, float y2,
			float width, float height, int direction, 
			boolean createComp) {
    this(createComp);
    if (orthogonal) { 
      if (onesided) { 
	edge = new OrthogonalSelfLoop(x1, y1, x2, y2, height, direction, false);
      } else { 
	edge = new OrthogonalSelfLoop4(x1, y1, x2, y2, width, height, direction, false);
      }
    } else { 
      if (onesided) { 
	edge = new CubicCurveSelfLoop(x1, y1, x2, y2, height, direction, false);
      } else { 
	edge = new PolyCurveSelfLoop(x1, y1, x2, y2, width, height, direction, false);
      }
    }
    edge.setParent(this);
    allowSelfLoop = edge.isAllowSelfLoop();
  }

  //////////////////////////////////////////////////////////////////////////////////

  public GraphEdgeBase getEdgeShape() {
    return edge; 
  }

  public void setEdgeShape(GraphEdgeBase e) { 
    edge = e; 
    if (edge != null) {
      edge.setEnds(p1, p2);

      edge.setHeadOffset(headOffset.dx, headOffset.dy);
      edge.setTailOffset(tailOffset.dx, tailOffset.dy);
      adjustOwnerBounds();
      edge.setParent(this);
    }
  }

  protected void adjustOwnerBounds() { 
    if (debug) System.out.println("GraphEdgeShape.adjustOwnerBounds()");

    if (edge != null) { 
      Point2D e1 = edge.getEnd1();
      Point2D e2 = edge.getEnd2();
      if (e1 != null && e2 != null) { 
	p1.setLocation(e1.getX(), e1.getY()); 
	p2.setLocation(e2.getX(), e2.getY()); 
	Rectangle2D b = edge.getBounds();
	Rectangle2D fb = edge.getFrameBounds();
	bounds.setRect(b.getX(), b.getY(), b.getWidth(), b.getHeight());
	framebounds.setRect(fb.getX(), fb.getY(), fb.getWidth(), fb.getHeight());
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////////////
  // GraphEdgeBase
  //////////////////////////////////////////////////////////////////////////////////

  /* Must not be overridden 
     public void doLayout();
  */

  @Override
  public Shape getOutlineShape() {
    if (edge != null) { 
      return edge.getOutlineShape();
    } else { 
      return super.getOutlineShape();
    }
  }

  @Override
  public void layoutComponents() {
    if (edge != null) { 
      edge.layoutComponents();
    } else { 
      super.layoutComponents();
    }
  }

  @Override
  public void adjustSelfLoop() {
    if (edge != null) { 
      edge.adjustSelfLoop();
      adjustOwnerBounds();
    } else { 
      super.adjustSelfLoop();
    }
  }

  @Override
  public void adjustEnds(GraphNodeBase src, GraphNodeBase des, boolean adjustCtrPts) {
    if (debug) System.out.println("GraphEdgeShape.adjustEnds()"); 

    if (edge != null) { 
      edge.adjustEnds(src, des, adjustCtrPts);
      //super.setEnds(edge.getEnd1(), edge.getEnd2());
      adjustOwnerBounds();
    }
  }

  @Override
  public void adjustControlPoints() {
    if (edge != null) { 
      edge.adjustControlPoints();
      adjustOwnerBounds();
    } else { 
      super.adjustControlPoints();
    }
  }

  @Override
  public void adjustEdge() { 
    if (edge != null) { 
      edge.adjustEdge();
      adjustOwnerBounds();
    } 
  }

  @Override
  public void setHeadOffset(float dx, float dy) {
    super.setHeadOffset(dx, dy);
    if (edge != null) { 
      edge.setHeadOffset(dx, dy);
    }
  }

  @Override
  public void setTailOffset(float dx, float dy) {
    super.setTailOffset(dx, dy);
    if (edge != null) { 
      edge.setTailOffset(dx, dy);
    }
  }

  @Override
  public void adjustTailOffset(Point2D p, GraphNodeBase node) {
    if (debug) System.out.println("GraphEdgeShape.adjustTailOffset()");
    if (edge != null) { 
      edge.adjustTailOffset(p, node);
      Offset offset = edge.getTailOffset(); 
      tailOffset.set(offset.dx, offset.dy);
    } else { 
      super.adjustTailOffset(p, node);
    }
  }

  @Override
  public void adjustHeadOffset(Point2D p, GraphNodeBase node) {
    if (debug) System.out.println("GraphEdgeShape.adjustHeadOffset()");
    if (edge != null) { 
      edge.adjustHeadOffset(p, node);
      Offset offset = edge.getHeadOffset(); 
      headOffset.set(offset.dx, offset.dy);
    } else { 
      super.adjustHeadOffset(p, node);
    }
  }

  @Override
  public void swapEnds() { 
    super.swapEnds();
    if (edge != null) { 
      edge.swapEnds();
    }
  }

  @Override
  public void setEdgeLayoutPolicy(int p) {
    super.setEdgeLayoutPolicy(p);
    if (edge != null) { 
      edge.setEdgeLayoutPolicy(p);
    }
  }

  @Override
  public void setAllowSelfLoop(boolean loop) {
    super.setAllowSelfLoop(loop);
    if (edge != null) { 
      edge.setAllowSelfLoop(loop);
    }
  }

  @Override
  public Label removeLabel(int role) {
    if (edge != null) { 
      return edge.removeLabel(role);
    }
    return null;
  }

  @Override
  public void removeLabel(Label label) {
    if (edge != null) { 
      edge.removeLabel(label);
    }
  }

  @Override
  public Label getLabelShape(int role) {
    if (edge != null) { 
      return edge.getLabelShape(role);
    }
    return null;
  }

  @Override
  public String getLabel(int role) {
    if (edge != null) { 
      return edge.getLabel(role);
    }
    return null;
  }

  @Override
  public void setLabel(int role, String labelText) {
    if (edge != null) {
      edge.setLabel(role, labelText);
    }
  }

  @Override
  public void setLabel(int role, Label label, boolean offset) {
    if (edge != null) {
      edge.setLabel(role, label, offset);
    }
  }

  @Override
  public void setLabelOffset(int role) {
    if (edge != null) {
      edge.setLabelOffset(role);
    }
  }

  @Override
  public void positionLabel(int role) { 
    if (edge != null) {
      edge.positionLabel(role);
    }
  }

  public boolean isFlippable() {
    if (edge != null) {
      return edge.isFlippable();
    }
    return false; 
  } 

  public void flipOrientation() {
    if (edge != null && 
	edge.isFlippable()) {
      edge.flipOrientation();
      adjustOwnerBounds();
    }
  } 

  //////////////////////////////////////////////////////////////////////////////////
  // CurveShape
  //////////////////////////////////////////////////////////////////////////////////

  public Point2D getCtr1() {
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      return curve.getCtr1();
    }
    return null;
  }

  public Point2D getCtr2() {
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      return curve.getCtr2();
    }
    return null;
  }

  public void setCtr1(float x, float y) {
   if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.setCtr1(x, y);
      adjustOwnerBounds();
    }
  }

  public void setCtr2(float x, float y) {
   if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.setCtr2(x, y);
      adjustOwnerBounds();
    }
  }

  public void setPoints(float x1, float y1, float x2, float y2) { 
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.setPoints(x1, y1, x2, y2);
      adjustOwnerBounds();
    }
  }

  public void setPoints(float x1, float y1, float x2, float y2, 
			float cx1, float cy1) { 
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.setPoints(x1, y1, x2, y2, cx1, cy1);
      adjustOwnerBounds();
    }
  }

  public void setPoints(float x1, float y1, float x2, float y2, 
			float cx1, float cy1, float cx2, float cy2) { 
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.setPoints(x1, y1, x2, y2, cx1, cy1, cx2, cy2);
      adjustOwnerBounds();
    }
  }

  public void moveEnd1Adj(float dx, float dy) { 
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.moveEnd1Adj(dx, dy);
      adjustOwnerBounds();
    } else { 
      super.moveEnd1(dx, dy); 
    }
  }

  public void moveEnd2Adj(float dx, float dy) { 
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.moveEnd2Adj(dx, dy);
      adjustOwnerBounds();
    } else { 
      super.moveEnd2(dx, dy); 
    }
  }

  public void moveCtr1(float dx, float dy) { 
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.moveCtr1(dx, dy);
      adjustOwnerBounds();
    } 
  }

  public void moveCtr2(float dx, float dy) { 
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.moveCtr2(dx, dy);
      adjustOwnerBounds();
    }
  }

  public void setEndsAdj(float x1, float y1, float x2, float y2,
			 boolean scaleCtrl) { 
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.setEndsAdj(x1, y1, x2, y2, scaleCtrl);
      adjustOwnerBounds();
    } else if (edge != null) { 
      edge.setEndsAdj(x1, y1, x2, y2);
      adjustOwnerBounds();
    } else { 
      super.setEndsAdj(x1, y1, x2, y2);
    }
  }

  public void scaleControlPoints(float x1, float y1, float x2, float y2) { 
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.scaleControlPoints(x1, y1, x2, y2);
      adjustOwnerBounds();
    }
  }

  public void flipControlPoints() { 
    if (edge instanceof CurveShape) { 
      CurveShape curve = (CurveShape) edge;
      curve.flipControlPoints();
      adjustOwnerBounds();
    }
  }

  public int getCurveType() {
    if (edge instanceof CubicCurveShape) { 
      CubicCurveShape curve = (CubicCurveShape) edge;
      return curve.getCurveType();
    }
    return -1;
  }

  public void setCurveType(int curveType) {
    if (edge instanceof CubicCurveShape) { 
      CubicCurveShape curve = (CubicCurveShape) edge;
      curve.setCurveType(curveType);
      adjustOwnerBounds();
    }
  }


  //////////////////////////////////////////////////////////////////////////////////
  // PolyShapeBase
  //////////////////////////////////////////////////////////////////////////////////

  public float getX() { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      return poly.getX();
    }
    return 0;
  }

  public float getY() { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      return poly.getY();
    }
    return 0;
  }

  public void setFirst(float x, float y) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.setFirst(x, y);
      adjustOwnerBounds();
    }
  }

  public void setStart(float x, float y) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.setStart(x, y);
      adjustOwnerBounds();
    }
  }

  public void addPoint() { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.addPoint();
      adjustOwnerBounds();
    }
  }

  public void addPoint(float x, float y) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.addPoint(x, y);
      adjustOwnerBounds();
    }
  }

  public void addPoint(float x, float y, boolean adj) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.addPoint(x, y, adj);
      adjustOwnerBounds();
    }
  }

  public void moveStartOnly(float dx, float dy) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.moveStartOnly(dx, dy);
      adjustOwnerBounds();
    }
  }

  public void moveVertex(int i, float dx, float dy) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.moveVertex(i, dx, dy);
      adjustOwnerBounds();
    }
  }

  public void setPoint(int i, float x, float y) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.setPoint(i, x, y);
      adjustOwnerBounds();
    }
  }

  public Point2D getPoint(int i) {
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      return poly.getPoint(i);
    }
    return null;
  }

  public java.util.List getPoints() { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      return poly.getPoints();
    }
    return null;
  }

  public void addPoint(int i, float x, float y) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.addPoint(i, x, y);
      adjustOwnerBounds();
    }
  }

  public void addPoint(int i, float x, float y, boolean adj) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.addPoint(i, x, y, adj);
      adjustOwnerBounds();
    }
  }

  public void removePoint(int i) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.removePoint(i);
      adjustOwnerBounds();
    }
  }

  public void setLast(float x, float y) { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.setLast(x, y);
      adjustOwnerBounds();
    }
  }

  public Point2D getLast() { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      return poly.getLast();
    }
    return null;
  }

  public int getNumberOfPoints() { 
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      return poly.getNumberOfPoints();
    }
    return 0;
  }

  public void copyPointsTo(PolyShape s) {
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.copyPointsTo(s);
      adjustOwnerBounds();
    }
  }

  public void scaleFirstLast(float x1, float y1, float x2, float y2) {
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.scaleFirstLast(x1, y1, x2, y2);
      adjustOwnerBounds();
    }
  }

  public void scaleFirstLast(float x1, float y1, float x2, float y2,
			     boolean limit) {
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.scaleFirstLast(x1, y1, x2, y2, limit);
      adjustOwnerBounds();
    }
  }


  public void drawPolyLine(Graphics2D g2) {
    if (edge instanceof PolyShapeBase) { 
      PolyShapeBase poly = (PolyShapeBase) edge; 
      poly.drawPolyLine(g2);
    }
  }

  public boolean isStartVertical() {
    if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      return oline.isStartVertical();
    }
    return false; 
  }

  public void setStartVertical(boolean startVertical) {
   if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      oline.setStartVertical(startVertical);
      adjustOwnerBounds();
    }
  }

  public boolean isRoundCorner() {
   if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      return oline.isRoundCorner();
    }
    return false; 
  }

  public void setRoundCorner(boolean roundCorner) {
   if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      oline.setRoundCorner(roundCorner);
      adjustOwnerBounds();
    }
  }

  public float getArcWidth() {
    if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      return oline.getArcWidth();
    }
    return 0; 
  }

  public float getArcHeight() {
    if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      return oline.getArcHeight();
    }
    return 0; 
  }

  public void setArcWidth(float arcw) {
    if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      oline.setArcWidth(arcw);
      adjustOwnerBounds();
    }
  }

  public void setArcHeight(float arch) {
    if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      oline.setArcHeight(arch);
      adjustOwnerBounds();
    }
  }

  public void setArc(float arcw, float arch) {
    if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      oline.setArc(arcw, arch);
      adjustOwnerBounds();
    }
  }

  public void flipDirection() { 
    if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      oline.flipDirection();
      adjustOwnerBounds();
    }
  }

  public void adjustOrientation() {
    if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      oline.adjustOrientation();
    }
  }

  public void normalizePoints() {
    if (edge instanceof OrthogonalLineShape) { 
      OrthogonalLineShape oline = (OrthogonalLineShape) edge;
      oline.normalizePoints();
    }
  }

  //////////////////////////////////////////////////////////////////////////////////
  // SelfLoop
  //////////////////////////////////////////////////////////////////////////////////

  public boolean isOrthogonal() { 
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      return loop.isOrthogonal();
    }
    return false; 
  }

  public boolean isOneSided() {
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      return loop.isOneSided();
    }
    return false; 
  }

  public int getDirection() {
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      return loop.getDirection();
    }
    return 0; 
  }

  public void setDirection(int direction) {
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      loop.setDirection(direction);
    }
  }

  public float getHeight() {
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      return loop.getHeight();
    }
    return 0; 
  }

  public void setHeight(float height) {
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      loop.setHeight(height);
    }
  }

  public float getWidth() {
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      return loop.getWidth();
    }
    return 0; 
  }

  public void setWidth(float width) { 
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      loop.setWidth(width);
    }
  }

  public boolean isAdjustable() { 
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      return loop.isAdjustable();
    }
    return false; 
  }

  public void setAdjustable(boolean b) { 
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      loop.setAdjustable(b);
    }
  }

  public void adjustDirection(Rectangle2D bounds, 
			      float x1, float y1, float x2, float y2) {
    if (edge instanceof SelfLoop) { 
      SelfLoop loop = (SelfLoop) edge; 
      loop.adjustDirection(bounds, x1, y1, x2, y2);
    }
  }

  //////////////////////////////////////////////////////////////////////////////////
  // DrawShape 
  //////////////////////////////////////////////////////////////////////////////////

  public DrawShape makeInstance() {
    return new GraphEdgeShape(comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    if (s != null) {
      super.copyTo(s);
      if (s instanceof GraphEdgeShape) {
	GraphEdgeShape e = (GraphEdgeShape) s; 
	if (edge != null) { 
	  e.edge = (GraphEdgeBase) edge.clone();  
	  e.edge.setParent(e);
	}
      }
    }
  }

  @Override
  public String getName() {
    String name = "GraphEdgeShape";
    if (edge != null) { 
      name += (":" + edge.getName());
    }
    return name;
  }

  @Override
  public void setClipSubShapes(boolean b) {
    super.setClipSubShapes(b);
    if (edge != null) { 
      edge.setClipSubShapes(b);
    }
  }

  @Override
  public void setCloneLabel(boolean cloneLabel) {
    super.setCloneLabel(cloneLabel);
    if (edge != null) { 
      edge.setCloneLabel(cloneLabel);
    }    
  }

  @Override
  public boolean isEdgeSensitive() {
    if (edge instanceof TwoEndsShape) { 
      return ((TwoEndsShape) edge).isEdgeSensitive();
    }
    return false;
  }

  @Override
  public boolean isCornorSensitive() {
    if (edge instanceof TwoEndsShape) { 
      return ((TwoEndsShape) edge).isCornorSensitive();
    }
    return true;
  }

  @Override
  public void setDrawAttr(DrawAttr attr) {
    super.setDrawAttr(attr);
    if (edge != null) { 
      edge.setDrawAttr(attr);
    }
  }

  @Override
  public void normalize() {
    // Must be empty for line/edges, override the super 
  }

  @Override
  public void setEnds(float x1, float y1, float x2, float y2) {
    if (edge != null) { 
      edge.setEnds(x1, y1, x2, y2);
      super.setEnds(x1, y1, x2, y2);
    } else { 
      super.setEnds(x1, y1, x2, y2);
    }
  }

  @Override
  public void setEndsAdj(float x1, float y1, float x2, float y2) {
    if (edge != null) { 
      edge.setEndsAdj(x1, y1, x2, y2);
      adjustOwnerBounds();
    } else { 
      super.setEndsAdj(x1, y1, x2, y2);
    }
  }

  @Override
  public void scale(float sx, float sy) {
    if (edge != null) { 
      edge.scale(sx, sy); 
      adjustOwnerBounds();
    } else { 
      super.scale(sx, sy); 
    }
  }

  @Override
  public void scaleEnds(float x1, float y1, float x2, float y2) {
    if (edge != null) { 
      edge.scaleEnds(x1, y1, x2, y2);
      adjustOwnerBounds();
    } else { 
      super.scaleEnds(x1, y1, x2, y2);
    }
  }

  @Override
  public void setPosition(float x, float y) {
    if (edge != null) { 
      edge.setPosition(x, y);
      adjustOwnerBounds();
    } else { 
      super.setPosition(x, y);
    }
  }

  @Override
  public void setCenterPosition(float x, float y) {
    if (edge != null) { 
      edge.setCenterPosition(x, y);
      adjustOwnerBounds();
    } else { 
      super.setCenterPosition(x, y);
    }
  }

  @Override
  public void move(float dx, float dy) {
    if (edge != null) { 
      edge.move(dx, dy);
      adjustOwnerBounds();
    } else { 
      super.move(dx, dy);
    }
  }

  @Override
  public void moveNotify(float dx, float dy) {
    if (edge != null) { 
      edge.moveNotify(dx, dy);
      adjustOwnerBounds();
    } else { 
      super.moveNotify(dx, dy);
    }
  }

  @Override
  public void setEnd1(float x, float y) {
    if (edge != null) { 
      edge.setEnd1(x, y);
      adjustOwnerBounds();
    } else { 
      super.setEnd1(x, y);
    }
  }

  @Override
  public void setEnd2(float x, float y) {
    if (edge != null) { 
      edge.setEnd2(x, y);
      adjustOwnerBounds();
    } else { 
      super.setEnd2(x, y);
    }
  }

  @Override
  public Point2D getEnd1() {
    if (edge != null) { 
      return edge.getEnd1();
    } else {
      return super.getEnd1();
    }
  }

  @Override
  public Point2D getEnd2() {
    if (edge != null) { 
      return edge.getEnd2();
    } else {
      return super.getEnd2();
    }
  }

  @Override
  public void moveEnd1(float dx, float dy) {
    if (edge != null) { 
      edge.moveEnd1(dx, dy);
      adjustOwnerBounds();
    } else { 
      super.moveEnd1(dx, dy);
    }
  }

  @Override
  public void moveEnd2(float dx, float dy) {
    if (edge != null) { 
      edge.moveEnd2(dx, dy);
      adjustOwnerBounds();
    } else { 
      super.moveEnd2(dx, dy);
    }
  }

  @Override
  public void moveCorner(int pos, float dx, float dy, boolean scaleSubshapes) {
    if (debug) System.out.println("GraphEdgeShape.moveCorner(scale) pos=" + pos);

    if (edge != null) { 
      edge.moveCorner(pos, dx, dy, scaleSubshapes);
      adjustOwnerBounds();
    } else { 
      super.moveCorner(pos, dx, dy, scaleSubshapes);
    }
  }

  @Override
  public void scaleComponents(float sx, float sy) {
    if (edge != null) { 
      edge.scaleComponents(sx, sy);
      adjustOwnerBounds();
    } else { 
      super.scaleComponents(sx, sy);
    }
  }

  @Override
  public void moveEdge(int pos, float dx, float dy, boolean scaleSubshapes) {
    if (edge != null) { 
      edge.moveEdge(pos, dx, dy, scaleSubshapes);
      adjustOwnerBounds();
    } else { 
      super.moveEdge(pos, dx, dy, scaleSubshapes);
    }
  }

  @Override
  public void moveCircumference(float px, float py) {
    if (edge != null) {
      edge.moveCircumference(px, py);
      adjustOwnerBounds();
    } else { 
      super.moveCircumference(px, py);
    }
  }

  @Override
  public void setHeadArrow(Arrow head) {
    super.setHeadArrow(head);
    if (edge != null) { 
      edge.setHeadArrow(head);
    }
  }

  @Override
  public void setTailArrow(Arrow tail) {
    super.setTailArrow(tail);
    if (edge != null) { 
      edge.setTailArrow(tail);
    }
  }

  @Override
  public void setArrowPosition() {
    super.setArrowPosition();
    if (edge != null) { 
      edge.setArrowPosition();
    }
  }
  @Override
  public void addArrows(ArrowAttr headArrowAttr, ArrowAttr tailArrowAttr) {
    super.addArrows(headArrowAttr, tailArrowAttr);
    if (edge != null) { 
      edge.addArrows(headArrowAttr, tailArrowAttr);
    }
  }

  @Override
  public void setBounds(float x1, float y1, float x2, float y2) {
    if (edge != null) { 
      edge.setBounds(x1, y1, x2, y2);
      adjustOwnerBounds();
    } else { 
      super.setBounds(x1, y1, x2, y2);
    }
  }

  /* Must not be overridden 
  public void calculateBounds() {
    if (edge != null) { 
      edge.calculateBounds();
      adjustOwnerBounds();
    } else {
      super.calculateBounds();
    }
  }
  */

  @Override
  public void setRotationAngle(float angle) {
    super.setRotationAngle(angle);
    if (edge != null) { 
      edge.setRotationAngle(angle);
      adjustOwnerBounds();
    }
  }

  @Override
  public void setToRotation(float vx, float vy) {
    super.setToRotation(vx, vy);
    if (edge != null) { 
      edge.setToRotation(vx, vy);
      adjustOwnerBounds();
    }
  }

  @Override
  public int isInside(Point2D p) {
   if (p != null) {
      float px = (float) p.getX();
      float py = (float) p.getY();
      return isInside(px, py);
    } else {
      return PointOutside;
    }
  }

  @Override
  public int isInside(float px, float py) {
    int result = PointOutside;
    if (edge != null) { 
      result = edge.isInside(px, py);
    }

    if (debug) System.out.println("GraphEdgeShape.isInside()=" + Integer.toHexString(result));

    return result; 
  }

  @Override
  public void drawFrame(Graphics2D g2) {
   if (edge != null) { 
      edge.drawFrame(g2);
    }
  }

  @Override
  public void drawOutline(Graphics2D g2, float x1, float y1, float x2, float y2) {
    if (edge != null) { 
      edge.drawOutline(g2, x1, y1, x2, y2);
    }
  }

  @Override
  public void drawOverHint(Graphics2D g2) {
    if (debug) System.out.println("GraphEdgeShape.drawOverHint()"); 

    if (edge != null) { 
      edge.drawOverHint(g2);
    }
  }

  @Override
  public void draw(Graphics2D g2) {
    if (edge != null) { 
      edge.draw(g2);
    }
  }

  @Override
  public void drawSubShapes(Graphics2D g2, int mode, DrawShape selectedShape,
			    Set<DrawShape> selectionSet) {
    if (edge != null) { 
      edge.drawSubShapes(g2, mode, selectedShape, selectionSet);
    }
  }

  @Override
  public DrawShapeSearch.Result searchSubShapes(Point2D p, 
						DrawShape exclude,
						boolean excludeOpenShape) {
    if (edge != null) { 
      return edge.searchSubShapes(p, exclude, excludeOpenShape);
    }
    return null;
  }

  @Override
  protected void writeXMLElements(CodeWriter out, int mode,
				  String namespacePrefix) {
    if (out != null) {
      super.writeXMLElements(out, mode, namespacePrefix);

      String ename = getPrefixedName("EdgeShape", namespacePrefix);
      out.indentInc();
      out.println("<" + ename + ">");
      if (edge != null) {
	edge.writeXML(out, mode, namespacePrefix);
      }
      out.thisIndentDec();
      out.println("</" + ename + ">");
    }
  }

  @Override
  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
    if (debug) System.out.println("GraphEdgeShape().processXMLElement()");

    if (e != null) {
      String name = e.getLocalName();
      if ("EdgeShape".equals(name)) {
	if (debug) System.out.println("GraphEdgeShape().processXMLElement() <EdgeShape>");

	org.w3c.dom.NodeList snodes = e.getChildNodes();
	if (snodes != null) {
	  int sn = snodes.getLength();
	  for (int j = 0; j < sn; j++) {
	    org.w3c.dom.Node snode = snodes.item(j);
	    String uri = snode.getNamespaceURI();
	    if (snode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && 
		(namespaceURI == null || namespaceURI.equals(uri))) {

	      if (debug) System.out.println("GraphEdgeShape().processXMLElement() " + snode.getLocalName());

	      GraphEdgeBase newEdge = 
		(GraphEdgeBase) DrawShapeBase.makeShapeFromXML((org.w3c.dom.Element) snode, 
							       namespaceURI);
	      if (newEdge != null) { 
		if (debug) System.out.println("GraphEdgeShape().processXMLElement() EdgeShape != null"); 
		edge = newEdge; 
	      }
	      if (edge != null) { 
		edge.setAttrFromXML((org.w3c.dom.Element) snode, namespaceURI);	     
	      }
	      //edge.calculateBounds();
	      //adjustOwnerBounds();
	    }
	  }
	}
      } else {
	super.processXMLElement(e, namespaceURI);
      }
    }
  }

  
  @Override
  public void setAttrFromXML(org.w3c.dom.Element node, String namespaceURI) {
    if (node != null) {
      //processXMLElement(node, namespaceURI); // to set edge shape  

      processXMLAttributes(node);
      setBaseAttrFromXML(node, namespaceURI);
      doLayout();
      //calculateBounds();
      adjustOwnerBounds();
    }
  }

  protected GraphEdgeBase edge; 

  protected static final boolean debug = false;

}


